﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
using GameObjects;
using Microsoft.Xna.Framework;
using Network;
using Virvar.Net;

namespace StillAlive
{
    class Communicator
    {
        public string ServerAddress = null;
        public int ServerConnectionPort = 7776;
        public int ServerPort = 7777;
        public int ClientPort = 7778;
        private int receiveTimeout = 5000;

        private NetPlayer _player;
        private Dictionary<int, Character> _characters = new Dictionary<int, Character>();
        private IObjMessager _inputMessager; // для входящих сообщений
        private IObjMessager _outputMessager; // для исходящих сообщений
        private int updateRate = 40; // период между обновлениями
        Timer sendTimer; // отправляет сообщения по таймеру
        private bool shouldContinue = true; // для завершения потока
        private LinkedList<ClientPacket> packetsQueue = new LinkedList<ClientPacket>();
        private LinkedList<Command> commandsQueue = new LinkedList<Command>();
        Vector2 previosPos;

        public Communicator(out Character player)
        {
            using (StreamReader sr = new StreamReader("Settings.txt"))
            {
                string line;
                while ((line = sr.ReadLine()) != null)
                {
                    if (line.StartsWith("[Server]"))
                    {
                        ServerAddress = line.Split('"')[1];
                        break;
                    }
                }
            }
            if (ServerAddress == null)
                throw new Exception("В Settings.txt отсутствует информация о сервере.");
            while (true)
            {
                try
                {
                    _inputMessager = new ProtoMessager<ServerPacket>(new UdpMessager(ClientPort, receiveTimeout));
                    _outputMessager = new ProtoMessager<ClientPacket>(new UdpMessager(ServerAddress, ServerPort));
                    break;
                }
                catch
                {
                    ClientPort++;
                }
            }
            // подключаемся к серверу
            ServerConnectionPacket connMsg = Connect();
            if (connMsg == null)
                Battlefield.Instance.Game.Exit();
            // обновление состояния игрока
            player = Battlefield.Instance.CreateCharacter(connMsg.PlayerId);
            player.Position = connMsg.Position;
            player.Speed = connMsg.Speed;
            player.moveBehavior = new PlayerMove1(player);
            _characters.Add(connMsg.PlayerId, player);
            _characters[connMsg.PlayerId].Position = previosPos = player.Position;

            _player = new NetPlayer(_outputMessager, player);
            _player.PlayerId = connMsg.PlayerId;
            // начинаем принимать сообщения
            new Thread(new ThreadStart(StartReceiving)).Start();
            // начинаем отправлять сообщения
            sendTimer = new Timer(new TimerCallback(Send), null, 0, updateRate);
        }

        ServerConnectionPacket Connect()
        {
            // подключаемся к серверу
            ConnectionMessage connMsg = new ConnectionMessage(ClientPort);
            ServerConnectionPacket inMsg = null;
            using (ProtoMessager<ConnectionMessage> connMessager =
                new ProtoMessager<ConnectionMessage>(new UdpMessager(ServerAddress, ServerConnectionPort)))
            {
                for (int i = 0; i < 10; i++)
                {
                    connMessager.Send(connMsg);
                    try
                    {
                        inMsg = (ServerConnectionPacket)_inputMessager.Receive<ServerConnectionPacket>();
                        break;
                    }
                    catch
                    {
                        // временно
                        Battlefield.Instance.PacketLength = -1;
                    }
                }
                if (inMsg != null)
                {
                    Trace.WriteLine("Connection succesful.");
                }
                else
                {
                    Trace.WriteLine("Failed to connect.");
                }
            }
            return inMsg;
        }

        void StartReceiving()
        {
            // запускаем поток получения сообщений от клиентов
            while (shouldContinue)
            {
                Receive();
            }
        }

        public void Stop()
        {
            shouldContinue = false;
            Battlefield.Instance.PacketLength = 0;
            _inputMessager.Close();
        }

        // добавляем нового игрока в игру
        void AddPlayer(PlayerInfo playerState)
        {
            Character character = Battlefield.Instance.CreateCharacter(playerState.PlayerId);
            _characters.Add(character.Id, character);
        }

        // удаление игрока, вышедшего из игры
        void RemovePlayer(PlayerInfo playerState)
        {
            _characters.Remove(playerState.PlayerId);
        }

        // отправка данных
        void Send(object state)
        {
            lock (packetsQueue)
            {
                ClientPacket outMsg = new ClientPacket(); // исходящее сообщение
                outMsg.Sequence = _player.MessageId++;
                outMsg.Ack = _player.Ack;
                outMsg.PlayerId = _player.PlayerId;
                outMsg.Time = DateTime.Now.TimeOfDay;
                //Trace.WriteLine(String.Format("{0} {1}", previosPos, _player.Character.Position));
                outMsg.Direction = _player.Character.Position - previosPos;
                // для прогнозирования
                commandsQueue.AddLast(new Command(outMsg.Time, outMsg.Direction));
                previosPos = _player.Character.Position;
                int x = outMsg.Direction.X > 0 ? 1 : outMsg.Direction.X < 0 ? -1 : 0;
                int y = outMsg.Direction.Y > 0 ? 1 : outMsg.Direction.Y < 0 ? -1 : 0;
                outMsg.Direction = new Vector2(x, y);
                _player.Messager.Send(outMsg);
                packetsQueue.AddLast(outMsg);
            }
        }

        // получение данных
        void Receive()
        {
            IPEndPoint remoteEP = null; // оправитель сообщения, здесь не используется
            ServerPacket packet = (ServerPacket)_inputMessager.Receive(ref remoteEP, out Battlefield.Instance.PacketLength);

            HashSet<int> activePlayers = new HashSet<int>();
            if (packet == null)
            {
                Stop();
                return;
            }
            if (packet.Sequence < _player.Ack)
            {
                return;
            }
            _player.Ack = packet.Sequence;
            foreach (PlayerInfo playerState in packet.PlayersStates)
            {
                activePlayers.Add(playerState.PlayerId);
                if (playerState.PlayerId != _player.PlayerId && !_characters.ContainsKey(playerState.PlayerId))
                {
                    // новое подключение
                    AddPlayer(playerState);
                    Trace.WriteLine("New Player connected.");
                }
                else if (playerState.PlayerId == _player.PlayerId)
                {
                    // обновление состояния игрока
                    lock (packetsQueue)
                    {
                        LinkedListNode<ClientPacket> packetNode = packetsQueue.First;
                        _player.Character.Position = playerState.Position;
                        while (packetNode != null && packetNode.Value.Sequence != packet.Ack)
                        {
                            packetNode = packetNode.Next;
                            packetsQueue.RemoveFirst();
                        }
                        if (packetNode != null)
                        {
                            LinkedListNode<Command> cmdNode = commandsQueue.First;
                            while (cmdNode != null && cmdNode.Value.Time <= packetNode.Value.Time)
                            {
                                cmdNode = cmdNode.Next;
                                commandsQueue.RemoveFirst();
                            }
                            while (cmdNode != null)
                            {
                                _player.Character.Position += cmdNode.Value.Delta;
                                cmdNode = cmdNode.Next;
                            }
                        }
                        previosPos = _player.Character.Position;
                    }
                }
                else
                {
                    _characters[playerState.PlayerId].Position = playerState.Position;
                }
            }
            HashSet<int> toRemove = new HashSet<int>();
            foreach (var id in _characters.Keys)
            {
                if (!activePlayers.Contains(id))
                    toRemove.Add(id);
            }
            foreach (var id in toRemove)
            {
                _characters.Remove(id);
                Battlefield.Instance.RemoveCharacter(id);
            }
        }
    }
}
